﻿using System;

namespace WindowsPhoneEssentials.Diagnostics
{
    public static class Trace
    {
        private static string traceStringFormat;
        private static Action traceScopeCallback;

        static Trace()
        {
            Appenders = new TraceAppenderCollection();
            Filters = new TraceFilterCollection();
            TraceLevel = TraceLevel.Warning;
            IndentSize = 4;
            ClassNameLength = 25;
        }

        public static int IndentLevel { get; private set; }
        public static int IndentSize { get; set; }

        /// <summary>
        /// Pads the class name up to this size (for standard width formatting)
        /// </summary>
        public static int ClassNameLength { get; set; }

        public static void Indent()
        {
            IndentLevel++;
        }

        public static void Unindent()
        {
            IndentLevel--;
        }

        public static TraceAppenderCollection Appenders { get; private set; }

        /// <summary>
        /// If you want to filter a particular trace source because it is too noisy, just add it to this collection
        /// </summary>
        public static TraceFilterCollection Filters { get; private set; }

        public static TraceLevel TraceLevel { get; set; }

        /// <summary>
        /// {0} for date, {1} for time, {2} for trace level, {3} for source class, {4} for trace message
        /// default: {0} {1} - {2} [{3}] - {4}
        /// example: 1/1/11 1:11 - Debug [SomeClass] - My debug message
        /// Set value to null to reset to default
        /// </summary>
        public static string TraceStringFormat
        {
            get { return traceStringFormat ?? "{0} {1} - {2} [{3}] - {4}"; }
            set { traceStringFormat = value; }
        }

        public static void WriteDebug(object source, Func<string> messageCallback)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            WriteDebug(source.GetType(), messageCallback);
        }

        public static void WriteDebug(Type sourceType, Func<string> messageCallback)
        {
            Write(TraceLevel.Debug, sourceType, messageCallback());
        }

        public static void WriteInfo(object source, Func<string> messageCallback)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            WriteInfo(source.GetType(), messageCallback);
        }

        public static void WriteInfo(Type sourceType, Func<string> messageCallback)
        {
            Write(TraceLevel.Info, sourceType, messageCallback());
        }

        public static void WriteWarning(object source, Func<string> messageCallback)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            WriteWarning(source.GetType(), messageCallback);
        }

        public static void WriteWarning(Type sourceType, Func<string> messageCallback)
        {
            Write(TraceLevel.Warning, sourceType, messageCallback());
        }

        public static void WriteError(object source, Func<string> messageCallback)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            WriteError(source.GetType(), messageCallback);
        }

        public static void WriteError(Type sourceType, Func<string> messageCallback)
        {
            Write(TraceLevel.Error, sourceType, messageCallback());
        }

        private static void Write(TraceLevel traceLevel, Type sourceType, string traceMessage)
        {
            if (sourceType == null)
                throw new ArgumentNullException("sourceType");

            if (traceMessage == null)
                throw new ArgumentNullException("traceMessage");

            if (IgnoreTraceFrom(traceLevel)) return;
            if (Appenders.Count == 0) return;
            if (Filters.Contains(sourceType)) return;

            if (traceScopeCallback != null)
            {
                traceScopeCallback();
                traceScopeCallback = null;
            }

            WriteInternal(traceLevel, sourceType, traceMessage);
        }

        private static void WriteInternal(TraceLevel traceLevel, Type sourceType, string traceMessage)
        {
            if (ClassNameLength < sourceType.Name.Length)
                ClassNameLength = sourceType.Name.Length;

            foreach (var appender in Appenders)
            {
                appender.WriteLine(string.Format(TraceStringFormat,
                                                 DateTime.Now.ToShortDateString(),
                                                 DateTime.Now.ToShortTimeString(),
                                                 traceLevel.ToString().PadRight(7),
                                                 sourceType.Name.PadRight(ClassNameLength),
                                                 string.Empty.PadLeft(IndentLevel*IndentSize) + traceMessage));
            }
        }

        internal static bool IgnoreTraceFrom(TraceLevel whatLevel)
        {
            return (int) TraceLevel > (int) whatLevel;
        }

        internal static void BeginTraceScope(Action<Action<TraceLevel, Type, string>> directWriteLine)
        {
            if (traceScopeCallback != null)
                traceScopeCallback();
            traceScopeCallback = () => directWriteLine(WriteInternal);
        }

        public static void ClearTraceScopeCallback()
        {
            traceScopeCallback = null;
        }
    }
}